package util

import (
	"archive/zip"
	"bufio"
	"crypto/md5"
	"fmt"
	"gitee.com/kingzyt/common/log"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	fp "path/filepath"
	"strings"
)

func AddTailSlashInPath(path string) string {
	path = strings.TrimSpace(path)
	path = strings.Replace(path, "\\", "/", -1)
	slash := strings.LastIndex(path, "/")
	if slash != len(path)-1 {
		path = path + "/"
	}
	return path
}

func GetDirName(path string) string {
	path = AddTailSlashInPath(path)
	path = path[:len(path)-1]
	return GetFileName(path)
}

func GetFileName(path string) string {
	path = strings.Replace(path, "\\", "/", -1)
	slash := strings.LastIndex(path, "/")
	path = path[slash+1:]
	dot := strings.LastIndex(path, ".")
	if dot >= 0 {
		return path[0:dot]
	} else {
		return path
	}
}
func GetFilePathWithoutName(path string) string {
	path = strings.Replace(path, "\\", "/", -1)
	slash := strings.LastIndex(path, "/")
	return path[0:slash]
}
func GetFilePostfix(path string) string {
	dot := strings.LastIndex(path, ".")
	if dot >= 0 {
		postfix := path[dot+1:]
		if len(postfix) > 20 {
			return ""
		} else {
			return postfix
		}
	} else {
		return ""
	}
}
func GetFileNameWithPostfix(path string) (fileName string) {
	path = strings.Replace(path, "\\", "/", -1)
	slash := strings.LastIndex(path, "/")
	if slash >= 0 {
		fileName = path[slash+1:]
	}
	return
}

func GetExecFullName() string {
	execfile, _ := exec.LookPath(os.Args[0])
	return execfile
}

//结尾无 /
func GetExecDir() string {
	execfile, _ := exec.LookPath(os.Args[0])
	execpath, _ := fp.Abs(execfile)
	execpath = strings.Replace(execpath, "\\", "/", -1)
	slash := strings.LastIndex(execpath, "/")
	if slash >= 0 {
		execpath = execpath[:slash]
	}
	return execpath
}

func GetExecName() string {
	execfile, _ := exec.LookPath(os.Args[0])
	return GetFileName(execfile)
}

func CalculateFileMd5(filepath string) (string, error) {
	f, err := os.Open(filepath)
	if err != nil {
		return "", err
	}
	defer f.Close()

	md5Ctx := md5.New()
	_, err = io.Copy(md5Ctx, f)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("%x", md5Ctx.Sum(nil)), nil
}

func ReadFile(readerName string, filepath string) (content []byte, ok bool) {
	content, err := ioutil.ReadFile(filepath)
	if err != nil {
		log.Error(readerName, "%s read err:%s", filepath, err)
		return
	}

	ok = true
	return
}

func IteratorFileLine(filepath string, proc func(lineStr string) bool) error {
	f, err := os.Open(filepath)
	if err != nil {
		return err
	}
	defer f.Close()

	return IteratorFileLineWithReader(f, proc)
}

func IteratorFileLineWithReader(rd io.Reader, proc func(lineStr string) bool) error {
	buff := bufio.NewReader(rd)
	for {
		lineStr, err := buff.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				if lineStr != "" {
					proc(lineStr) //final line
				}
				break
			}
			return err
		}

		if !proc(lineStr) {
			break
		}
	}
	return nil
}
func IteratorFileLineWithReader2(rd io.Reader, proc func(lineStr string) error) error {
	buff := bufio.NewReader(rd)
	for {
		lineStr, err := buff.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				if lineStr != "" {
					err = proc(lineStr) //final line
					if err != nil {
						return err
					}
				}
				break
			}
			return err
		}

		err = proc(lineStr)
		if err != nil {
			return err
		}
	}
	return nil
}

// only walk just the target folder, donot go into sub folder
func IteratorFileFolder(path string, proc func(filepath string, f os.FileInfo)) error {
	return fp.Walk(path, func(filePath string, f os.FileInfo, err error) error {
		if f == nil {
			return err
		}
		if f.IsDir() {
			if filePath != path {
				return fp.SkipDir
			}
		} else {
			proc(filePath, f)
		}
		return nil
	})
}
func IteratorFileFolderForDir(path string, proc func(dir string, f os.FileInfo)) error {
	return fp.Walk(path, func(filePath string, f os.FileInfo, err error) error {
		if f == nil {
			return err
		}
		if f.IsDir() && filePath != path {
			proc(filePath, f)
			return fp.SkipDir
		}
		return nil
	})
}

func WriteFile(filepath string, content string, flag int) error {
	outfile, err := os.OpenFile(filepath, flag, 0660)
	if err != nil {
		return err
	}

	defer outfile.Close()

	writer := bufio.NewWriter(outfile)
	defer writer.Flush()

	c := content
	for {
		n, err := writer.WriteString(c)
		if err != nil {
			return err
		}
		if n < len(c) {
			c = c[n:]
		} else {
			return nil
		}
	}
}

func WriteBinFile(filepath string, content []byte, flag int) error {
	outfile, err := os.OpenFile(filepath, flag, 0660)
	if err != nil {
		return err
	}

	defer outfile.Close()

	writer := bufio.NewWriter(outfile)
	defer writer.Flush()

	c := content
	for {
		n, err := writer.Write(c)
		if err != nil {
			return err
		}
		if n < len(c) {
			c = c[n:]
		} else {
			return nil
		}
	}
}

func MkDir(folderPath string) error {
	f, _ := os.Stat(folderPath)
	if f == nil {
		err := os.MkdirAll(folderPath, 0770)
		if err != nil {
			log.Error("", "folderPath(%s) mk fail, err: %s", folderPath, err)
		}
		return err
	}
	return nil
}

func CopyFile(dstName, srcName string) (written int64, err error) {
	src, err := os.Open(srcName)
	if err != nil {
		return
	}
	defer src.Close()
	dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return
	}
	defer dst.Close()
	return io.Copy(dst, src)
}

func ZipFiles(targetW io.Writer, files map[string][]byte) error {
	w := zip.NewWriter(targetW)
	for fileName, content := range files {
		f, err := w.Create(fileName)
		if err != nil {
			return err
		}
		_, err = f.Write(content)
		if err != nil {
			return err
		}
	}

	err := w.Close()
	if err != nil {
		return err
	}
	return nil
}

func ScanTableFileContent(rd io.Reader, rowProc func(row []string) error) (err error) {
	fileErr := IteratorFileLineWithReader(rd, func(lineStr string) bool {
		lineStr = strings.TrimSpace(lineStr)
		if lineStr == "" {
			return true
		}

		var strs []string
		SplitOnStrListCore("", lineStr, "\t", func(str string) bool {
			strs = append(strs, str)
			return true
		})

		err = rowProc(strs)
		return err == nil
	})
	if err != nil {
		return err
	}
	if fileErr != nil {
		return fileErr
	}
	return nil
}
